# -*- coding: utf-8 -*-
import logging
import sys

from cantools import db  # cantools==37.2.0
from tkinter import filedialog, messagebox
from openpyxl.reader.excel import load_workbook
from cantools.database import Message, Signal, dump_file
from os.path import splitext, basename, dirname, join, normpath
import tkinter as tk
import re


class StreamToLogger:
    def __init__(self, logger, level):
        self.logger = logger
        self.level = level
        self.linebuf = ''

    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.level, line.rstrip())

    def flush(self):
        pass


def num_seq(value):
    """
    将获取的格子内的字符串转换为整型
    """
    if isinstance(value, str):
        return int(re.sub(r'\s+', '', value))
    return value


def create_value_tables(value_cell):
    """
    给信号添加键值对表
    """
    value_table = {}
    if value_cell is None:
        return value_table
    for line in value_cell.split('\n'):
        try:
            # 分割键和值
            parts = line.split(':', 1)
            key_part, value = parts
            # if '~' in key_part:
            #     # 分割范围起始值
            #     range_parts = key_part.split('~')
            #     start, end = range_parts
            #     # 转为16进制整数
            #     start_num = int(start, 16)
            #     end_num = int(end, 16)
            #     # 生成范围内的所有键值
            #     for num in range(start_num, end_num + 1):
            #         value_table[f"0x{num:x}"] = value
            # else:
            key_part = int(key_part, 16)
            value_table[key_part] = value
        except Exception as e:
            print(f"Error processing line: {line}. Error: {e}")
            return {}
    return value_table


def create_dbc(file_path, save_path):
    """
    循环创建信号和帧消息，生成dbc
    """
    excel = load_workbook(file_path)
    sheet = excel.worksheets[0]
    total_rows = sheet.max_row

    # 已创建的消息列表
    message_list = []

    # 当前创建的消息和信号的信息
    message_info = {}
    signal_info = {}
    signal_list = []

    # 计数
    count_message = 0
    count_signal = 0

    for row in sheet.iter_rows(min_row=3, values_only=True):
        if row[0] is not None:
            if count_message > 0:
                message = Message(**message_info, signals=signal_list)
                message_list.append(message)

                # 清除当前字典、列表信息
                signal_list.clear()
                message_info.clear()
                signal_info.clear()
                count_signal = 0
                print(f'第{count_message}帧完成')

                # 退出
                # if count > 70:
                #     break
            message_info = {
                'frame_id': int(row[2].rstrip("x")[2:], 16),
                'name': re.sub(r'\s+', '', row[0]),
                'length': row[5],
                'cycle_time': row[4],
                'is_extended_frame': True
            }

            count_message += 1
        else:
            value_table = create_value_tables(row[24])
            signal_info = {
                'name': re.sub(r'\s+', '', row[6]),
                'start': num_seq(row[10]),
                'length': num_seq(row[12]),
                'is_signed': row[13].replace(" ", "").lower() != "unsigned",
                'scale': num_seq(row[14]),
                'offset': num_seq(row[15]),
                'minimum': num_seq(row[16]),
                'maximum': num_seq(row[17]),
                'choices': value_table,
                'unit': row[7],
                'comment': row[7]
            }

            signal = Signal(**signal_info)
            signal_list.append(signal)

    message = Message(**message_info, signals=signal_list)
    message_list.append(message)
    print(f'第{count_message}帧完成')

    dbc = db.Database(message_list)
    dump_file(dbc, save_path, encoding="GBK")

def main_window():
    """
    创建图形化界面
    """
    # 创建窗口
    root = tk.Tk()
    root.title("Excel to DBC")

    # 文件选择输入框
    frame = tk.Frame(root)
    frame.pack(padx=10, pady=10)

    entry_file = tk.Entry(frame, width=50)
    entry_file.pack(side=tk.LEFT, fill=tk.X, expand=True)

    button_file = tk.Button(frame, text="选择文件", command=lambda: choose_file(entry_file))
    button_file.pack(side=tk.RIGHT)

    # 处理按钮
    process_button = tk.Button(root, text="生成DBC", command=lambda: process_files(entry_file))
    process_button.pack(side=tk.BOTTOM, pady=10)

    # 运行主循环
    root.mainloop()

def choose_file(entry_file):
    """
    创建选择框
    """
    file_path = filedialog.askopenfilename(title="选择Excel文件", filetypes=[("Excel files", "*.xlsx;*.xls")])
    if file_path:
        entry_file.delete(0, tk.END)
        entry_file.insert(0, file_path)


def process_files(entry_file):
    """
    读取excel，定义保存dbc路径，保存日志文件
    """
    file_path = entry_file.get()
    if not file_path:
        messagebox.showerror("错误", "请选择文件")
        return

    save_name, extension = splitext(basename(file_path))
    save_name += ".dbc"
    save_path = normpath(join(dirname(file_path), save_name))

    # 创建一个logger
    logger = logging.getLogger(__name__)
    # 日志级别
    logger.setLevel(logging.DEBUG)
    # 文件日志处理器
    file_handler = logging.FileHandler('app.log', mode='w')
    file_handler.setLevel(logging.DEBUG)
    file_formatter = logging.Formatter('%(asctime)s %(levelname)s:%(message)s')
    file_handler.setFormatter(file_formatter)
    logger.addHandler(file_handler)

    # 控制台日志处理器（使用pyinstaller --onefile --windowed -i icon/pack.ico --clean .\excel_to_dbc.py 打包时需要注释掉）
    # console_handler = logging.StreamHandler(sys.stdout)
    # console_handler.setLevel(logging.DEBUG)
    # console_formatter = logging.Formatter('%(asctime)s %(levelname)s:%(message)s')
    # console_handler.setFormatter(console_formatter)
    # logger.addHandler(console_handler)

    stdout_logger = StreamToLogger(logger, logging.INFO)
    stderr_logger = StreamToLogger(logger, logging.ERROR)
    sys.stdout = stdout_logger
    sys.stderr = stderr_logger

    try:
        create_dbc(file_path, save_path)
    except Exception as e:
        logger.exception(e)

    messagebox.showinfo("完成", "文件处理完成，保存至：" + save_path)


if __name__ == "__main__":
    main_window()

    # 测试用例
    # path = "./BEV4.5T_PCAN_DYXYFQBEVTXV2000-20211016改PDU报文周期.xlsx"
    # create_dbc(file_path=path, save_path="./test.dbc")

